﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace WindowsGame1
{
    class Maze
    {
        Cell[][] maze;
        int sideANumCells;
        int sideBNumCells;

        public Maze(int sideANumCells, int sideBNumCells)
        {
            this.sideANumCells = sideANumCells;
            this.sideBNumCells = sideBNumCells;
            maze = new Cell[sideANumCells][];

            for (int x = 0; x < sideANumCells; x++)
            {
                maze[x] = new Cell[sideBNumCells];

                for (int y = 0; y < sideBNumCells; y++)
                {
                    Cell cur = new Cell(x, y);
                    cur.InitializeWalls(); // create 4 wall objects that will never be deleted
                    maze[x][y] = cur;

                    if (x == 0)
                    {
                        cur.LockLeft();
                    }
                    if (x == sideANumCells - 1)
                    {
                        cur.LockRight();
                    }
                    if (y == 0)
                    {
                        cur.LockBottom();
                    }
                    if (y == sideBNumCells - 1)
                    {
                        cur.LockTop();
                    }


                }
            }

            CreatePaths();
        }

        public Cell GetCell(int x, int y)
        {
            return maze[x][y];
        }

        public void CreatePaths()
        {
            Random ran = new Random();
            List<Wall> walls = new List<Wall>();

            maze[0][0].SetInMaze(true);
            walls.Add(new Wall(maze[0][0], CellSide.TOP));
            walls.Add(new Wall(maze[0][0], CellSide.RIGHT));
            walls.Add(new Wall(maze[0][0], CellSide.BOTTOM));
            walls.Add(new Wall(maze[0][0], CellSide.LEFT));

            while (walls.Count != 0)
            {
                int num = ran.Next(walls.Count - 1);
                Wall wall = walls[num];
                Cell curCell = wall.GetCell();
                CellSide curEdge = wall.GetEdge();
                Cell nextCell;

                if (!curCell.IsLocked(curEdge))
                {
                    int x = curCell.GetX();
                    int y = curCell.GetY();

                    switch (curEdge)
                    {
                        case CellSide.TOP:
                            {
                                nextCell = maze[x][y + 1];
                                break;
                            }
                        case CellSide.BOTTOM:
                            {
                                nextCell = maze[x][y - 1];
                                break;
                            }
                        case CellSide.LEFT:
                            {
                                nextCell = maze[x - 1][y];
                                break;
                            }
                        default:
                            {
                                nextCell = maze[x + 1][y];
                                break;
                            }
                    }

                    if (!nextCell.IsInMaze())
                    {
                        walls.Add(new Wall(nextCell, CellSide.TOP));
                        walls.Add(new Wall(nextCell, CellSide.BOTTOM));
                        walls.Add(new Wall(nextCell, CellSide.LEFT));
                        walls.Add(new Wall(nextCell, CellSide.RIGHT));
                        nextCell.SetInMaze(true);
                        curCell.Set(curEdge, !Cell.WALL);
                        nextCell.Set(GetOppositeEdge(curEdge), !Cell.WALL);
                    }
                    else
                    {
                        walls.Remove(wall);
                    }

                }
                else
                {
                    walls.Remove(wall);
                }
            }

            // Now that we're done making paths, update the up wall objects for each cell
            for (int x = 0; x < sideANumCells; x++)
            {
                for (int y = 0; y < sideBNumCells; y++)
                {
                    maze[x][y].UpdateUpWalls();
                }
            }

        }

        public Cell GetAdjacentCell(Cell currentCell, CellSide edge)
        {
            switch (edge)
            {
                case CellSide.TOP:
                    return maze[currentCell.GetX()][currentCell.GetY() + 1];
                case CellSide.BOTTOM:
                    return maze[currentCell.GetX()][currentCell.GetY() - 1];
                case CellSide.RIGHT:
                    return maze[currentCell.GetX() + 1][currentCell.GetY()];
                default:
                    return maze[currentCell.GetX() - 1][currentCell.GetY()];
            }
        }

        public CellSide GetOppositeEdge(CellSide edge)
        {
            switch (edge)
            {
                case CellSide.TOP:
                    return CellSide.BOTTOM;
                case CellSide.BOTTOM:
                    return CellSide.TOP;
                case CellSide.LEFT:
                    return CellSide.RIGHT;
                default:
                    return CellSide.LEFT;

            }
        }



        public void PrintMaze()
        {
            for (int x = 0; x < sideANumCells; x++)
            {
                for (int y = 0; y < sideBNumCells; y++)
                {
                    Console.Out.WriteLine("Cell " + x + ", " + y + ":");
                    Cell c = maze[x][y];
                    for (int i = 0; i < 4; i++)
                    {
                        switch ((CellSide)i)
                        {
                            case CellSide.TOP:
                                Console.Out.WriteLine("TOP: " + (c.GetWall((CellSide)i) == true ? "--" : " "));
                                break;
                            case CellSide.RIGHT:
                                Console.Out.WriteLine("RIGHT: " + (c.GetWall((CellSide)i) == true ? "|" : " "));
                                break;
                            case CellSide.BOTTOM:
                                Console.Out.WriteLine("BOTTOM: " + (c.GetWall((CellSide)i) == true ? "--" : " "));
                                break;
                            case CellSide.LEFT:
                                Console.Out.WriteLine("LEFT: " + (c.GetWall((CellSide)i) == true ? "|" : " "));
                                break;
                        }
                    }
                }
            }
        }

        public int CountRows()
        {
            return maze.Count<Cell[]>();
        }

        public int CountCols()
        {
            return maze[0].Count<Cell>();
        }

        public List<Cell> GetInaccessibleCells(Cell playerLocation)
        {
            for (int x = 0; x < maze.Length; x++)
            {
                for (int y = 0; y < maze[x].Length; y++)
                {
                    maze[x][y].SetInMaze(false);
                }
            }

            playerLocation.SetInMaze(true);

            MarkAccessibleCells(playerLocation);
            Cell currentCell;

            List<Cell> result = new List<Cell>();

            for (int x = 0; x < maze.Length; x++)
            {
                for (int y = 0; y < maze[x].Length; y++)
                {
                    if (!(currentCell = maze[x][y]).IsInMaze())
                    {
                        result.Add(currentCell);
                    }
                }
            }

            return result;
        }

        private void MarkAccessibleCells(Cell currentLocation)
        {
            Cell nextCell;
            if(!currentLocation.GetWall(CellSide.TOP))
            {
                nextCell = maze[currentLocation.GetX()][currentLocation.GetY() + 1];
                if (!nextCell.IsInMaze())
                {
                    nextCell.SetInMaze(true);
                    MarkAccessibleCells(nextCell);
                }
            }

            if (!currentLocation.GetWall(CellSide.BOTTOM))
            {
                nextCell = maze[currentLocation.GetX()][currentLocation.GetY() - 1];
                if (!nextCell.IsInMaze())
                {
                    nextCell.SetInMaze(true);
                    MarkAccessibleCells(nextCell);
                }
            }

            if (!currentLocation.GetWall(CellSide.LEFT))
            {
                nextCell = maze[currentLocation.GetX() - 1][currentLocation.GetY()];
                if (!nextCell.IsInMaze())
                {
                    nextCell.SetInMaze(true);
                    MarkAccessibleCells(nextCell);
                }
            }

            if (!currentLocation.GetWall(CellSide.RIGHT))
            {
                nextCell = maze[currentLocation.GetX() + 1][currentLocation.GetY()];
                if (!nextCell.IsInMaze())
                {
                    nextCell.SetInMaze(true);
                    MarkAccessibleCells(nextCell);
                }
            }
        }

        internal IEnumerable<Cell> GetCells()
        {
            //List<Cell> list = maze.Cast<Cell>().ToList();
            List<Cell> list = new List<Cell>();
            for (int x = 0; x < sideANumCells; x++)
            {
                for (int y = 0; y < sideBNumCells; y++)
                {
                    list.Add(maze[x][y]);
                }
            }
            return list;
        }



        private CellSide GetSide(int edge)
        {
            switch (edge)
            {
                case 0:
                    return CellSide.TOP;
                case 1:
                    return CellSide.BOTTOM;
                case 2:
                    return CellSide.LEFT;
                default:
                    return CellSide.RIGHT;
            }
        }


        public void ShiftWalls(Cell playerLocation, CellSide edgeOne, CellSide edgeTwo)
        {
            Random ran = new Random();
            int num1, num2, num3;

            for (int x = 0; x < 5; x++)
            {
                Cell currentCell;
                CellSide currentSide;
                do
                {
                    num1 = ran.Next(CountRows());
                    num2 = ran.Next(CountRows());
                    num3 = ran.Next(4);
                } while ((currentCell = GetCell(num1, num2)).GetWall(currentSide = GetSide(num3)) ||
                    (currentCell == playerLocation && (currentSide == edgeOne || currentSide == edgeTwo)));

                Cell adjacentCell = GetAdjacentCell(currentCell, currentSide);
                currentCell.Set(currentSide, true);
                adjacentCell.Set(GetOppositeEdge(currentSide), true);
                adjacentCell.UpdateUpWalls();
                currentCell.UpdateUpWalls();
            }

            List<Cell> inaccessibleCells = GetInaccessibleCells(playerLocation);

            while (inaccessibleCells.Count() != 0)
            {
                num1 = ran.Next(inaccessibleCells.Count());
                num3 = ran.Next(4);
                Cell currentCell = inaccessibleCells[num1];
                Cell adjacentCell;
                CellSide side = GetSide(num3);
                if (currentCell.GetWall(side) && !currentCell.IsLocked(side)
                    && (adjacentCell = GetAdjacentCell(currentCell, side)).IsInMaze())
                {
                    currentCell.Set(side, false);
                    adjacentCell.Set(GetOppositeEdge(side), false);
                    inaccessibleCells = GetInaccessibleCells(playerLocation);
                    adjacentCell.UpdateUpWalls();
                    currentCell.UpdateUpWalls();
                }
            }

        }

        // TODO add wall, add animation (possibly) to show wall coming down slower
        internal void Update(Microsoft.Xna.Framework.Vector3 cameraLocation)
        {
            int x = (int)Math.Floor(cameraLocation.X / 200.0); // if at 333, then 333/200 = 1.65, rounded to 2, minus 1 because 0 indexed array
            int z = (int)Math.Floor(cameraLocation.Z / 200.0); // FIX
            if (x < 0 || x >= sideANumCells || z < 0 || z >= sideBNumCells)
                return;
            Cell currCell = maze[x][z];

            CellSide closeEdgeX;
            CellSide closeEdgeZ;

            float cellCenterWorldX = x * 200 + 100;
            float cellCenterWorldZ = z * 200 + 100;

            if (cameraLocation.X >= cellCenterWorldX) // right side of cell
                closeEdgeX = CellSide.RIGHT;
            else
                closeEdgeX = CellSide.LEFT;

            if (cameraLocation.Z >= cellCenterWorldZ) // top part of cell
                closeEdgeZ = CellSide.TOP;
            else
                closeEdgeZ = CellSide.BOTTOM;

            ShiftWalls(currCell, closeEdgeX, closeEdgeZ);
            //Random r = new Random();
            //int edge = r.Next(4);
            //CellSide curEdge;
            //Cell nextCell = null;
            //bool locked = false;
            //switch (edge)
            //{
            //    case 0:
            //        curEdge = CellSide.TOP;
            //        if (currCell.IsLocked(CellSide.TOP))
            //            locked = true;
            //        else
            //            nextCell = maze[x][z + 1];
            //        break;
            //    case 1:
            //        curEdge = CellSide.BOTTOM;
            //        if (currCell.IsLocked(CellSide.BOTTOM))
            //            locked = true;
            //        else
            //            nextCell = maze[x][z - 1];
            //        break;
            //    case 2:
            //        curEdge = CellSide.RIGHT;
            //        if (currCell.IsLocked(CellSide.RIGHT))
            //            locked = true;
            //        else
            //            nextCell = maze[x + 1][z];
            //        break;
            //    default:
            //        curEdge = CellSide.LEFT;
            //        if (currCell.IsLocked(CellSide.LEFT))
            //            locked = true;
            //        else
            //            nextCell = maze[x - 1][z];
            //        break;
            //}

            //if (!locked)
            //{
            //    maze[x][z].Set(curEdge, !Cell.WALL);
            //    maze[x][z].UpdateUpWalls();
            //    nextCell.Set(GetOppositeEdge(curEdge), !Cell.WALL);
            //    nextCell.UpdateUpWalls();
            //}
        }

        internal Microsoft.Xna.Framework.Vector3 FindNextCellToProceedTo(Vector3 monsterLocation, Vector3 cameraLocation)
        {
            int monsterX = (int)Math.Floor(monsterLocation.X / 200.0); // if at 333, then 333/200 = 1.65, rounded to 2, minus 1 because 0 indexed array
            int monsterZ = (int)Math.Floor(monsterLocation.Z / 200.0);
            int playerX = (int)Math.Floor(cameraLocation.X / 200.0); // if at 333, then 333/200 = 1.65, rounded to 2, minus 1 because 0 indexed array
            int playerZ = (int)Math.Floor(cameraLocation.Z / 200.0);
            if (monsterX < 0 || monsterX >= sideANumCells || monsterZ < 0 || monsterZ >= sideBNumCells)
                return Vector3.Zero;
            if (playerX < 0 || playerX >= sideANumCells || playerZ < 0 || playerZ >= sideBNumCells)
                return Vector3.Zero;

            Cell nextCell = findNextCell(maze[monsterX][monsterZ], maze[playerX][playerZ]);
            Vector3 directionToGo = new Vector3(-(monsterX - nextCell.GetX()) * 200, 0, -(monsterZ - nextCell.GetY()) * 200);
            return directionToGo;
        }

        // Uses breadth-first
        private Cell findNextCell(Cell fromCell, Cell toCell)
        {
            //Stack<Cell> path = new Stack<Cell>();
            for (int x = 0; x < sideANumCells; x++)
            {
                for (int y = 0; y < sideBNumCells; y++)
                {
                    maze[x][y].unmarkFromBFS();
                    maze[x][y].setPreviousCellForBFS(null);
                }
            }

            Queue<Cell> queue= new Queue<Cell>();
            queue.Enqueue(toCell);
            toCell.markForBFS();
            while (queue.Count > 0)
            {
                Cell curr = queue.Dequeue();
                if (curr.GetX() == fromCell.GetX() && curr.GetY() == fromCell.GetY())
                {
                    // while curr.getPreviousCellForBFS() != null, path.push(curr.getPreviousCellForBFS()) if whole path wanted
                    // also would need to switch fromCell to toCell and vice versa, or use a dequeue instead of stack
                    Cell previousCell = curr.getPreviousCellForBFS();
                    if (previousCell == null) // we're at the cell already, no need to move anywhere
                        return fromCell;
                    else
                        return previousCell;
                }
                foreach (Cell c in getAdjacentAccessibleCells(curr))
                {
                    if (!c.isMarkedForBFS())
                    {
                        c.markForBFS();
                        c.setPreviousCellForBFS(curr);
                        queue.Enqueue(c);
                    }
                }

            }
            return fromCell;
        }

        private IEnumerable<Cell> getAdjacentAccessibleCells(Cell currentCell)
        {
            List<Cell> surroundingCells = new List<Cell>();

            if (!currentCell.GetWall(CellSide.LEFT))
                surroundingCells.Add(maze[currentCell.GetX() - 1][currentCell.GetY()]);
            if (!currentCell.GetWall(CellSide.TOP))
                surroundingCells.Add(maze[currentCell.GetX()][currentCell.GetY() + 1]);
            if (!currentCell.GetWall(CellSide.RIGHT))
                surroundingCells.Add(maze[currentCell.GetX() + 1][currentCell.GetY()]);
            if (!currentCell.GetWall(CellSide.BOTTOM))
                surroundingCells.Add(maze[currentCell.GetX()][currentCell.GetY() - 1]);

            return surroundingCells;
        }

        public bool sameCell(Vector3 location0, Vector3 location1)
        {
            int loc0x = (int)Math.Floor(location0.X / 200.0); // if at 333, then 333/200 = 1.65, rounded to 2, minus 1 because 0 indexed array
            int loc0z = (int)Math.Floor(location0.Z / 200.0);
            int loc1x = (int)Math.Floor(location1.X / 200.0); // if at 333, then 333/200 = 1.65, rounded to 2, minus 1 because 0 indexed array
            int loc1z = (int)Math.Floor(location1.Z / 200.0);

            if (loc0x == loc1x && loc0z == loc1z)
                return true;

            return false;
        }
    }
}
